<?php

/*
 *  Copyright (C) 2010 Emanuele Colonnelli <emanuele.colonnelli@phpstruct.com>
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

namespace org\phpstruct\core;

use org\phpstruct\core\dto;

const ERROR_XML_PARSER_NOT_VALID = -10001;
const ERROR_XML_XMAP_NOT_FOUND = -10002;
const ERROR_XML_XMAP_VALIDATION = -10003;
const ERROR_XML_NOT_YET_LOADED = -10004;

/**
 * Description of XMap
 *
 * @author Emanuele Colonnelli <emanuele.colonnelli@phpstruct.com>
 */
class XMap {

    private $dom;
    private $xmap;
    private $plugin = null;
    private $errors = array();

    public function __construct($plugin=null) {
        $this->errors = array();
        $this->init();
    }

    private function init() {
        try {
            $cache = CacheFactory::getInstance()->factory(CacheFactory::SESSION);
            $this->xmap = $cache->get('XMAP');
            if (\is_array($this->xmap)) {
                return 1;
            }

            \libxml_use_internal_errors(true);

            $ds = \DIRECTORY_SEPARATOR;
            $xml_path = "{$_SERVER['DOCUMENT_ROOT']}{$ds}.phpstruct{$ds}conf{$ds}xmap.xml";

            if (!\file_exists($xml_path)) {
                throw new \Exception(_('Could not find xmap.xml'), ERROR_XML_XMAP_NOT_FOUND, null);
            }

            $this->dom = new \DOMDocument('1.0', 'utf-8');
            $this->dom->validateOnParse = false;
            $this->dom->load($xml_path);
            if (!@$this->dom->validate()) {
                throw new exceptions\XMapException();
            }
        } catch (\Exception $e) {
            throw $e;
        }
    }

    /**
     *
     * @return <type> 
     */
    public function parse() {

        if (\is_array($this->xmap)) {
            return $this->xmap;
        }

        $struct = array();
        if ($this->dom instanceof \DOMDocument) {

            $xpath = new \DOMXPath($this->dom);

            $uris = $xpath->query('//uri', $this->dom);
            foreach ($uris as $uri) {
                if ($uri instanceof \DOMNode) {
                    $uriAttr = $xpath->query('./@path', $uri);
                    $uriPath = $uriAttr->item(0)->nodeValue;


                    /* ################################################
                     * HELPER
                     */
                    $helper = $this->parseHelpers($xpath, $uri);

                    /* ###############################################
                     * PLUGIN
                     */
                    $xplugin = $this->parsePlugin($xpath, $uri);
                    /* ###############################################
                     * CLASS
                     */
                    $xclass = $this->parseClass($xpath, $uri);

                    $struct[$this->normalizeURI($uriPath)] = array(
                        'class' => $xclass,
                        'plugin' => $xplugin,
                        'helper' => $helper
                    );
                }
            }
            $cache = CacheFactory::getInstance()->factory(CacheFactory::SESSION);

            $cache->set('XMAP', $struct);
            return $struct;
        } else {
            throw new \Exception(_("XMpath is not yet loaded"), ERROR_XML_NOT_YET_LOADED, null);
        }
    }

    /**
     *
     * @param \DOMXPath $xpath
     * @param <type> $uri 
     */
    private function parseClass(\DOMXPath $xpath, $uri) {
        $xclass = null;
        $classes = $xpath->query('./class', $uri);
        if ($classes instanceof \DOMNodeList) {
            for ($i = 0; $i < $classes->length; $i++) {
                $_name = $xpath->query('./@name', $classes->item($i));
                $_method = $xpath->query('./@method', $classes->item($i));

                /* ##############################################
                 * 		PARAMS
                 */
                $_params = $this->_getParamNodes($classes->item($i), $xpath);

                $xclass = new dto\XMapClass($this->_getNodesValues($_name), $this->_getNodesValues($_method), $_params);
            }
        }
        return $xclass;
    }

    /**
     *
     * @param \DOMXPath $xpath
     * @param <type> $params
     * @return array of dto\XMapHelper
     */
    private function parseHelpers(\DOMXPath &$xpath, $uri) {
        $helper = $xpath->query('./helper', $uri);
        $helpers = array();
        if ($helper instanceof \DOMNodeList) {
            for ($i = 0; $i < $helper->length; $i++) {
                $_h = $helper->item($i);
                $_h_params = $this->_getParamNodes($_h);
                $_h_name = $this->_getAttributeByName($_h, 'name');
                $h = new dto\XMapHelper($_h_name->value, $_h_params);
                \array_push($helpers, $h);
            }
        }
        return $helpers;
    }
    
        /**
     *
     * @param \DOMXPath $xpath
     * @param <type> $params
     * @return array of dto\XMapFilter
     */
    private function parseFilters(\DOMXPath &$xpath, $uri) {
        $filter = $xpath->query('./filter', $uri);
        $filters = array();
        if ($filter instanceof \DOMNodeList) {
            for ($i = 0; $i < $filter->length; $i++) {
                $_h = $filter->item($i);
                $_h_params = $this->_getParamNodes($_h);
                $_h_name = $this->_getAttributeByName($_h, 'name');
                $h = new dto\XMapFilter($_h_name->value, $_h_params);
                \array_push($filters, $h);
            }
        }
        return $filters;
    }

    /**
     *
     * @param \DOMXPath $xpath
     * @param <type> $uri
     * @return dto\XMapPlugin 
     */
    private function parsePlugin(\DOMXPath $xpath, $uri) {
        $plugin = $xpath->query('./plugin', $uri);
        $xplugin = null;
        if ($plugin instanceof \DOMNodeList) {
            for ($i = 0; $i < $plugin->length; $i++) {
                $_plg = $plugin->item($i);
                $_plg_params = $this->_getParamNodes($_plg);
                $_plg_name = $this->_getAttributeByName($_plg, 'name');
                $xplugin = new dto\XMapPlugin($_plg_name->value, $_plg_params);
            }
        }
        return $xplugin;
    }

    private function parseEvents(\DOMXPath $xpath=null) {
        if (!($xpath instanceof \DOMXPath) || is_null($xpath)) {
            $xpath = new \DOMXPath($this->dom);
        }

        $xevents = $xpath->query('//event', $this->dom);
    }

    private function _getMatchingURL($url) {
        $path = parse_url($url, PHP_URL_PATH);

        $matches = array();
        if (isset($this->xmap)) {
            foreach ($this->xmap as $_url => $item) {

                if (preg_match("~" . \addslashes($_url) . "~", $path)) {
                    $matches[$_url] = $item;
                }
            }
        }

        if (count($matches) > 1) {
            \trigger_error(_("Ambiguos XMAP, two or more possible matching for ") . $path, \E_USER_NOTICE);
        }

        return $matches;
    }

    public function getURI($uri) {
        try {
            $uri = parse_url($uri, PHP_URL_PATH);

            $match = @$this->_getMatchingURL($uri);

            if (count($match) > 0) {
                while ($xnode = \array_shift($match)) {

                    if (isset($xnode['helper']) && !is_null($xnode['helper'])) {
                        if (is_array($xnode['helper'])) {
                            while($h = array_shift($xnode['helper'])){
                                InvocationQueue::getInstance()->pushHelper($h);
                            }
                        } else
                            if ($xnode['helper'] instanceof dto\XMapHelper) {
                            InvocationQueue::getInstance()->pushHelper($xnode['helper']);
                        }
                    }
                    if (isset($xnode['class']) && !is_null($xnode['class'])) {
                        InvocationQueue::getInstance()->pushClass($xnode['class']);
                    }
                }
                InvocationQueue::getInstance()->invokeList();
            } else {
                View::load();
            }
        } catch (org\phpstruct\core\exceptions\StructException $e) {
            throw $e;
        }
    }

    private function normalizeURI($uri) {
        $uri = parse_url($uri, PHP_URL_PATH);
        return $uri;
    }

    /**
     *
     * @param \DOMNode $node
     * @param <type> $nodeName
     * @param \DOMXPath $xpath
     * @return DOMElement
     */
    private function _getAttributeByName(\DOMElement $node, $nodeName, \DOMXPath $xpath = null) {
        if ($node instanceof \DOMElement) {
            if (!($xpath instanceof \DOMXPath))
                $xpath = new \DOMXPath($this->dom);
            $attribute = $xpath->query("./@{$nodeName}", $node);
            if ($attribute instanceof \DOMNodeList) {
                return $attribute->item(0);
            }
        }
        return null;
    }

    private function _getParamNodes(\DOMElement $node, \DOMXPath $xpath = null) {
        if ($node instanceof \DOMElement) {
            if (!($xpath instanceof \DOMXPath)) {
                $xpath = new \DOMXPath($this->dom);
            }
            $xparams = $xpath->query('./param', $node);
            $_params = null;
            if ($xparams instanceof \DOMNodeList) {
                for ($j = 0; $j < $xparams->length; $j++) {
                    if (is_null($_params))
                        $_params = array();
                    $_p = $xparams->item($j);
                    $_pname = $xpath->query('./@name', $_p);
                    $_pvalue = $xpath->query('./@value', $_p);
                    $_prequired = $xpath->query('./@required', $_p);

                    $pname = $this->_getNodesValues($_pname);
                    $pvalue = $this->_getNodesValues($_pvalue);
                    $prequired = $this->_getNodesValues($_prequired);

                    $param = new dto\XMapParam($pname, $pvalue, $prequired);
                    array_push($_params, $param);
                }
            }
            return $_params;
        } else {
            return null;
        }
    }

    private function _getNodesValues(\DOMNodeList $nodes) {
        if ($nodes instanceof \DOMNodeList) {
            if ($nodes->length > 1) {
                $ret = array();
                for ($i = 0; $i < $nodes->lenght; $i++) {
                    \array_push($ret, $nodes->item($i)->value);
                }
                return $ret;
            } else {
                if (!is_null($nodes->item(0))) {
                    return $nodes->item(0)->value;
                }
                return null;
            }
        } else {
            return null;
        }
    }
}
